﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using Amib.Threading;
using MLSharp.Utilities;

namespace MLSharp
{
	/// <summary>
	/// Represents a partition of training and validation data
	/// produced by <see cref="KFold"/> for k-fold cross
	/// validation.
	/// </summary>
	public class Partition
	{
		#region Private Fields

		/// <summary>
		/// The training data set.
		/// </summary>
		private readonly BasicDataSet mTraining;

		/// <summary>
		/// The validation data set.
		/// </summary>
		private readonly BasicDataSet mValidation;

		#endregion

		#region Public Properties

		/// <summary>
		/// The instances to use for training.
		/// </summary>
		public IDataSet Training 
		{
			get { return mTraining; }
		}

		/// <summary>
		/// The instances to use for validation.
		/// </summary>
		public IDataSet Validation
		{
			get { return mValidation; }
		}

		#endregion

		#region Public Constructors

		/// <summary>
		/// Instantiates a data partition that will hold instances
		/// with the specified attributes.
		/// </summary>
		/// <param name="attributes"></param>
		public Partition(IEnumerable<DataAttribute> attributes)
		{
			mTraining = new BasicDataSet();
			mTraining.Attributes.AddRange(attributes);

			mValidation = new BasicDataSet();
			mValidation.Attributes.AddRange(attributes);
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Creates a human-readable representation of the partition, useful 
		/// for testing purposes.
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			StringBuilder s = new StringBuilder();

			s.Append("Training{");

			foreach (Instance instance in mTraining.Instances)
			{
				s.AppendFormat("[{0}]", instance);
			}

			s.Append("}");

			s.Append("Validation{");

			foreach (Instance instance in mValidation.Instances)
			{
				s.AppendFormat("[{0}]", instance);
			}

			s.Append("}");

			return s.ToString();
		}

		#endregion

		#region Private Static Methods

		/// <summary>
		/// Callback used by <see cref="SmartThreadPool"/>.
		/// </summary>
		/// <param name="o">A <see cref="Pair{TLeft,TRight}"/> containing the partition
		/// to process and an action to run.</param>
		/// <returns></returns>
		private static object RunAction(object o)
		{
			Pair<Partition, Action<Partition>> wrapper = (Pair<Partition, Action<Partition>>) o;

			//This is invoking "Action(partition)".
			wrapper.Right(wrapper.Left);

			return null;
		}

		#endregion

		#region Public Static Methods

		/// <summary>
		/// Provides a way to run an action on a set of partitions in parallel.
		/// </summary>
		/// <param name="partitions"></param>
		/// <param name="action"></param>
		/// <remarks>
		/// This will spawn a maximum of one thread per processor to run the action over the partitions.
		/// *I added this because the PFX/System.Threading support was insufficient for what I needed.  
		/// </remarks>
		public static void ParallelForEach(Partition[] partitions, Action<Partition> action)
		{
			STPStartInfo startInfo = new STPStartInfo();

			if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["MaxProcessors"]))
			{
				startInfo.MaxWorkerThreads = int.Parse(ConfigurationManager.AppSettings["MaxProcessors"]);
			}
			else
			{
				startInfo.MaxWorkerThreads = Environment.ProcessorCount;
			}

			SmartThreadPool threadPool = new SmartThreadPool(startInfo);
			
			foreach (Partition p in partitions)
			{
				Pair<Partition, Action<Partition>> wrapper = new Pair<Partition, Action<Partition>>(p, action);

				threadPool.QueueWorkItem(RunAction, wrapper);
			}

			threadPool.WaitForIdle();
		}


		#endregion
	}
}
